﻿using System;
using System.Collections.Generic;
using System.Device.Location;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Threading;
using FastestWays.Helpers;
using FastestWays.Helpers.Coordinates;
using FastestWays.Helpers.Voice;
using FastestWays.Messages;
using FastestWays.Model;
using FastestWays.Model.WazeObjects;
using FastestWays.Server;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using Microsoft.Phone.Maps.Controls;
using Microsoft.Phone.Shell;
using WazePocWP8.Server;
using Windows.Devices.Geolocation;
using Windows.Foundation;
using Windows.Phone.Speech.Synthesis;

namespace FastestWays.ViewModel
{
    /// <summary>
    /// This class contains properties that the main View can data bind to.
    /// <para>
    /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
    /// </para>
    /// <para>
    /// You can also use Blend to data bind with the tool's support.
    /// </para>
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        private const int TimerTimeout = 10000;
        private Geolocator _locationWatcher;
        Dispatcher _dispatcher = Deployment.Current.Dispatcher;
        public event TypedEventHandler<MainViewModel, EventArgs> PositionChangedOnMain;
        private Timer _centerTimer;
        private SettingsEngine _settingsEngine;
        private IMessageHandler _messageHandler = AppServicesLocator.MessageHandler;

        protected virtual void OnPositionChangedOnMain(EventArgs args)
        {
            TypedEventHandler<MainViewModel, EventArgs> handler = PositionChangedOnMain;
            if (handler != null) handler(this, args);
        }

        public MainViewModel()
        {
            SetAllCommands();

            SettingsEnabledFromPhoneSettings = true;

            _settingsEngine = new SettingsEngine();
            _settingsEngine.LoadIsLocationServiceOnSettingFromIso();
            _locationWatcher = new Geolocator() { DesiredAccuracy = PositionAccuracy.High, MovementThreshold = 1 };
            _locationWatcher.StatusChanged += LocationWatcherStatusChanged;

            CenterGeocoordinate = new GeoCoordinate(47.669444, -122.123889);

            RouteLine = new List<MapElement>();

            SetMapModeByTime();

            PhoneApplicationService.Current.Closing += CurrentClosing;

            if (!Parameters.IsDataServicesOn)
            {
                _messageHandler.DisplayMessage("No data services available, this app will not work without data. Please turn on the data services in the phone's settings");
            }
        }

        private void SetMapModeByTime()
        {
            Daytime = MapColorMode.Light;
            //if (DateTime.Now.ToLocalTime() )
            //{
                
            //}
        }

        private void ActivateViewAccordingToLocationServiceActivation(bool locationServicesOn)
        {
            if (!_dispatcher.CheckAccess())
            {
                _dispatcher.BeginInvoke(() =>
                    {
                        RaiseCanExecuteOnCommands();
                    });
            }
            else
            {
                RaiseCanExecuteOnCommands();
            }

            if (locationServicesOn)
            {
                _centerTimer = new Timer(delegate { StopMapMovingByFinger(); }, null, Timeout.Infinite, TimerTimeout);
                ArrivalTime = "זמן הגעה:";
                _locationWatcher.PositionChanged += LocationWatcherPositionChanged;
            }
            else
            {
                _centerTimer = null;
                _locationWatcher.PositionChanged -= LocationWatcherPositionChanged;
                ArrivalTime = "זמן הגעה:";
            }
        }

        private void RaiseCanExecuteOnCommands()
        {
            GoToAddressesSearchCommand.RaiseCanExecuteChanged();
            GoBackToCenterCommand.RaiseCanExecuteChanged();
            GoToSettingsCommand.RaiseCanExecuteChanged();
        }

        void LocationWatcherStatusChanged(Geolocator sender, StatusChangedEventArgs args)
        {

            if (args.Status == PositionStatus.Disabled)
            {
                SettingsEnabledFromPhoneSettings = false;
                ActivateViewAccordingToLocationServiceActivation(false);
            }
            else
            {
                SettingsEnabledFromPhoneSettings = true;
                if (Parameters.IsLocationServiceOn)
                    ActivateViewAccordingToLocationServiceActivation(true);
                else
                {
                    ActivateViewAccordingToLocationServiceActivation(false);
                }
            }

        }

        private void SetAllCommands()
        {
            GoToAddressesSearchCommand =
                new RelayCommand(
                    () =>
                    {
                        Messenger.Default.Send<NavigateToPageMessage>(new NavigateToPageMessage()
                            {
                                PageName = "AddressesSearch"
                            });
                    }, () => Parameters.IsLocationServiceOn && SettingsEnabledFromPhoneSettings);

            GoToSettingsCommand =
                new RelayCommand(
                    () =>
                    { Messenger.Default.Send<NavigateToPageMessage>(new NavigateToPageMessage() { PageName = "Settings" }); }, () => SettingsEnabledFromPhoneSettings);

            GoToAboutCommand =
                new RelayCommand(
                    () => { Messenger.Default.Send<NavigateToPageMessage>(new NavigateToPageMessage() { PageName = "About" }); });

            GoBackToCenterCommand = new RelayCommand(() => { StopMapMovingByFinger(); }, () => Parameters.IsLocationServiceOn && SettingsEnabledFromPhoneSettings);
        }

        private void StopMapMovingByFinger()
        {
            _centerTimer.Change(Timeout.Infinite, TimerTimeout);
            MapMovingByFinger = false;
        }

        void CurrentClosing(object sender, ClosingEventArgs e)
        {
            if (_wazeEngine == null && _locationWatcher != null)
            {
                _locationWatcher.PositionChanged -= LocationWatcherPositionChanged;
            }
            _settingsEngine.SaveIsLocationServiceOnSettingToIso();

        }

        public async void OnNavigatedTo(NavigationEventArgs navigationEventArgs)
        {
            ActivateViewAccordingToLocationServiceActivation(Parameters.IsLocationServiceOn);

            if (Parameters.IsLocationServiceOn && SettingsEnabledFromPhoneSettings)
            {
                var startuplocation = await GetCurrentGeocoordinates();
                //Parameters.Coordinate = new GeoCoordinate(32.114451, 34.850985);

                if (Parameters.Coordinate != null)
                {
                    _wazeEngine = new WazeEngine(startuplocation);
                    await _wazeEngine.GetRoute();
                    if (_wazeEngine.RouteWazeResponse != null)
                    {
                        PaintMapOverlay(_wazeEngine.RouteWazeResponse);
                        DisplayDriveTime();
                    }
                    else
                    {
                        _messageHandler.DisplayMessage("Navigation is not possible for this location");
                    }
                }
            }
        }

        private void DisplayDriveTime()
        {
            var totalTime = _wazeEngine.GetTotalDriveTime();
            var arrival = (DateTime.Now + TimeSpan.FromSeconds(totalTime)).ToString("HH:mm");
            ArrivalTime = "זמן הגעה: " + arrival;
        }

        private void LocationWatcherPositionChanged(Geolocator geolocator, PositionChangedEventArgs args)
        {
            if (_wazeEngine != null && _wazeEngine.RouteWazeResponse != null)
            {
                GetNearbyInstructions(args.Position.Coordinate);
                CheckIfDestinationReached(args.Position.Coordinate);
            }
            var convertedCoordinate = CoordinateConverter.ConvertGeocoordinate(args.Position.Coordinate);
            CurrentLocation = convertedCoordinate;

            if (!MapMovingByFinger)
            {
                CenterGeocoordinate = convertedCoordinate;
                RotateMapAccordingToDirectionOfDriving(args.Position.Coordinate);
                DrawPrintScreenForDisplayOnIcon(args.Position.Coordinate);
            }
            _lastCoordinate = args.Position.Coordinate;
        }

        private void DrawPrintScreenForDisplayOnIcon(Geocoordinate coord)
        {
            if (_lastPicCoordinate != null)
            {
                double distanceTo = coord.ToGeoCoordinate().GetDistanceTo(_lastPicCoordinate.ToGeoCoordinate());
                if (distanceTo > 20)
                {
                    OnPositionChangedOnMain(new EventArgs());
                    _lastPicCoordinate = coord;
                }
            }
            else
            {
                _lastPicCoordinate = coord;
            }
        }

        private void CheckIfDestinationReached(Geocoordinate coordinate)
        {
            if (_wazeEngine.DetrmainIfLastCoordinate(coordinate))
            {
                RouteLine = new List<MapElement>();
                _wazeEngine = null;
            }
        }

        private Geocoordinate _lastCoordinate;
        private void RotateMapAccordingToDirectionOfDriving(Geocoordinate coord)
        {
            if (_lastCoordinate != null)
            {
                double distanceTo = coord.ToGeoCoordinate().GetDistanceTo(_lastCoordinate.ToGeoCoordinate());
                if (distanceTo > 2)
                    MapHeading = CoordinatesMath.Bearing(_lastCoordinate.Latitude, _lastCoordinate.Longitude,
                                            coord.Latitude, coord.Longitude);
            }
        }

        private async void GetNearbyInstructions(Geocoordinate coord)
        {
            var nearbyItems = _wazeEngine.GetNearbyItems(coord);
            if (nearbyItems.Any())
            {
                var result = nearbyItems.First();
                _wazeEngine.SetWasReadOnInstruction(result, true);

                if (result.instruction != null)
                {
                    await ReadInstruction(result);
                }
            }
        }

        private async Task ReadInstruction(Result result)
        {
            string parsedString;
            if (result.instruction.arg != 0)
            {
                object[] args = new[] { new[] { result.instruction.arg } };
                parsedString = VoiceInstructions.GetParsedInstructionFromDictionary(result.instruction.opcode, args);
            }
            else
            {
                parsedString = VoiceInstructions.GetParsedInstructionFromDictionary(result.instruction.opcode);
            }

            if (!string.IsNullOrEmpty(parsedString))
            {
                SpeechSynthesizer synth = new SpeechSynthesizer();
                await synth.SpeakTextAsync(parsedString);
            }
        }

        private async Task<Geocoordinate> GetCurrentGeocoordinates()
        {
            Geolocator myGeolocator = new Geolocator();
            Geoposition myGeoposition = await myGeolocator.GetGeopositionAsync();
            Geocoordinate myGeocoordinate = myGeoposition.Coordinate;
            return myGeocoordinate;
        }


        private List<GeoCoordinate> _pointsList;

        private void PaintMapOverlay(RootObject myWazeResponse)
        {
            RouteLine = new List<MapElement>();
            _pointsList = new List<GeoCoordinate>(myWazeResponse.coords.Count);

            foreach (var coord in myWazeResponse.coords)
            {
                //Why are they wrong for waze? can I at least rename them?
                _pointsList.Add(new GeoCoordinate(coord.y, coord.x));
            }
            if (_pointsList.Any())
                PositionPointsOnMap();
        }

        private void PositionPointsOnMap()
        {
            var mapElements = new List<MapElement>();
            var linePointList = new List<GeoCoordinate>();
            Result startPointInInstruction = null;
            foreach (var geoCoordinate in _pointsList)
            {
                if (startPointInInstruction == null)
                {
                    startPointInInstruction = _wazeEngine.GetFirstPoint();
                }

                var endPointInInstruction = _wazeEngine.GetPointInInstruction(geoCoordinate);

                //Adding points to the line according to the traffic algorithem
                linePointList.Add(geoCoordinate);

                if (endPointInInstruction != null && startPointInInstruction != endPointInInstruction)
                {
                    Color trafficColor = DeterminColorByTraffic(startPointInInstruction);
                    MapPolyline path = new MapPolyline() { StrokeColor = trafficColor, StrokeThickness = 10 };
                    foreach (var lineCoord in linePointList)
                    {
                        path.Path.Add(lineCoord);
                    }

                    mapElements.Add(path);

                    linePointList.Clear();
                    linePointList.Add(geoCoordinate);
                    startPointInInstruction = endPointInInstruction;
                }
            }

            RouteLine = mapElements;
        }

        private Color DeterminColorByTraffic(Result startPointInInstruction)
        {
            if (startPointInInstruction.crossTimeWithoutRealTime != 0)
            {
                double trafficTime = (double)startPointInInstruction.crossTime / startPointInInstruction.crossTimeWithoutRealTime;
                if (trafficTime > 1.5)
                {
                    return Colors.Red;
                }
                else if (trafficTime > 1.3)
                {
                    return Colors.Orange;
                }
                else if (trafficTime > 1.1)
                {
                    return Colors.Yellow;
                }
                else
                {
                    return Colors.Magenta;
                }
            }
            return Colors.Magenta;
        }


        public RelayCommand GoBackToCenterCommand
        {
            get;
            private set;
        }

        public RelayCommand GoToAddressesSearchCommand
        {
            get;
            private set;
        }

        public RelayCommand GoToAboutCommand
        {
            get;
            private set;
        }

        public RelayCommand ExitCommand
        {
            get;
            private set;
        }

        public RelayCommand GoToSettingsCommand
        {
            get;
            private set;
        }

        private GeoCoordinate _currentLocation;
        public GeoCoordinate CurrentLocation
        {
            get { return _currentLocation; }
            set
            {
                _currentLocation = value;
                if (!_dispatcher.CheckAccess())
                {
                    _dispatcher.BeginInvoke(() => { RaisePropertyChanged("CurrentLocation"); });
                }
                else
                {
                    RaisePropertyChanged("CurrentLocation");
                }
            }
        }

        private List<MapElement> _routeLine;
        private GeoCoordinate _centerGeocoordinate;
        private double _mapHeading;
        private string _arrivalTime;
        private WazeEngine _wazeEngine;
        private Geocoordinate _lastPicCoordinate;
        private bool _mapMovingByFinger;
        private bool _settingsEnabledFromPhoneSettings;
        private MapColorMode _daytime;

        public List<MapElement> RouteLine
        {
            get { return _routeLine; }
            set
            {
                _routeLine = value;
                RaisePropertyChanged("RouteLine");
            }
        }

        public GeoCoordinate CenterGeocoordinate
        {
            get { return _centerGeocoordinate; }
            set
            {
                if (value != null)
                {
                    _centerGeocoordinate = value;
                    RaisePropertyChanged("CenterGeocoordinate");
                }
            }
        }

        public double MapHeading
        {
            get { return _mapHeading; }
            set
            {
                _mapHeading = value;
                try
                {
                    RaisePropertyChanged("MapHeading");
                }
                catch (Exception)
                {
                }
            }
        }

        public string ArrivalTime
        {
            get { return _arrivalTime; }
            set
            {
                _arrivalTime = value;
                if (!_dispatcher.CheckAccess())
                {
                    _dispatcher.BeginInvoke(() => { RaisePropertyChanged("ArrivalTime"); });
                }
                else
                {
                    RaisePropertyChanged("ArrivalTime");
                }
            }
        }

        public bool MapMovingByFinger
        {
            get { return _mapMovingByFinger; }
            set
            {
                _mapMovingByFinger = value;
                if (value)
                {
                    _centerTimer.Change(TimerTimeout, TimerTimeout);
                }
            }
        }

        public bool SettingsEnabledFromPhoneSettings
        {
            get { return _settingsEnabledFromPhoneSettings; }
            set
            {
                _settingsEnabledFromPhoneSettings = value;
                if (!_dispatcher.CheckAccess())
                {
                    _dispatcher.BeginInvoke(() =>
                    {
                        RaisePropertyChanged("SettingsEnabledFromPhoneSettings");
                    });
                }
                else
                {
                    RaisePropertyChanged("SettingsEnabledFromPhoneSettings");

                }
            }
        }

        public MapColorMode Daytime
        {
            get { return _daytime; }
            set
            {
                _daytime = value;
                RaisePropertyChanged("Daytime");
            }
        }
    }
}



